import * as secp from "@noble/secp256k1";
import { hmac } from "@noble/hashes/hmac";
import { sha256 } from "@noble/hashes/sha256";
import { keccak256 } from "ethereum-cryptography/keccak";
import rlp from "rlp";
import { bytesToHex, hexToBytes, bufBE, toChecksumAddress, bigintToHex } from "./utils";
import type { Provider, Hex as RpcHex } from "./provider";

// 为 noble v3 配置同步 hash 实现（用于 RFC6979 HMAC-DRBG）
// - noble 的 ECDSA 签名默认会对消息做 sha256（prehash=true）
// - 我们在以太坊交易中已经用 keccak256 计算了 digest，因此必须传 prehash=false
// - 这里注册同步 hmacSha256 / sha256，供 noble 的确定性 k 生成使用
secp.hashes.hmacSha256 = (key: Uint8Array, msg: Uint8Array) => hmac(sha256, key, msg);
secp.hashes.sha256 = sha256;

export type Address = `0x${string}`; // 以太坊地址（EIP-55 校验和形式）
export type Hex = `0x${string}`; // 0x 前缀的十六进制字符串

// secp256k1 曲线阶 & 半阶：用于显式低-S 规范化
// 解释：ECDSA 的 (r, s) 存在可塑性：若 s > n/2，可以替换为 n - s 得到等价签名
// 以太坊 / 比特币节点通常要求 "低 S"（s <= n/2），避免签名可塑性带来的问题
const CURVE_N = BigInt("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141");
const CURVE_HALF_N = CURVE_N >> 1n;

function addressFromPrivateKey(privHex: Hex): Address {
  // 原理：
  // 1) 用私钥导出未压缩公钥（65 字节，前缀 0x04 + 64 字节坐标）
  // 2) 对公钥的 64 字节坐标做 keccak256（去掉前缀 0x04）
  // 3) 取哈希后 20 字节作为地址，再做 EIP-55 校验和
  const raw = privHex.startsWith("0x") ? privHex.slice(2) : privHex;
  const priv = hexToBytes("0x" + raw);
  const pub = secp.getPublicKey(priv, false); // uncompressed 65 bytes
  const hash = keccak256(pub.slice(1)); // drop 0x04
  const addr = bytesToHex(hash.slice(12)); // last 20 bytes
  return toChecksumAddress(addr) as Address;
}

export interface Eip1559Tx {
  // EIP-1559 typed transaction v2 的核心字段（EIP-2718 封装）
  chainId: number; // 链 ID（测试网 / 主网）
  nonce: bigint; // 发送账户的交易计数器
  maxPriorityFeePerGas: bigint; // 小费（矿工优先费）
  maxFeePerGas: bigint; // 用户愿意支付的 gas 单价上限
  gasLimit: bigint; // 交易可消耗的最大 gas 单位数
  to?: Address; // 目标地址（合约创建交易时为空）
  value?: bigint; // 转账金额（单位：wei）
  data?: Uint8Array; // 调用数据（合约交互）
  accessList?: any[]; // 访问列表（本例为空）
}

export class Wallet {
  readonly address: Address;
  private priv: Uint8Array; // 私钥字节
  constructor(privateKey: Hex, private provider: Provider) {
    const raw = privateKey.startsWith("0x") ? privateKey.slice(2) : privateKey;
    const privHex = ("0x" + raw) as Hex;
    this.priv = hexToBytes(privHex);
    this.address = addressFromPrivateKey(privHex);
  }

  async signEip1559(tx: Eip1559Tx): Promise<Hex> {
    // 交易负载（不含 [yParity, r, s]）：按 EIP-1559 的字段顺序 RLP 编码
    const payload = [
      bufBE(BigInt(tx.chainId)),
      bufBE(tx.nonce),
      bufBE(tx.maxPriorityFeePerGas),
      bufBE(tx.maxFeePerGas),
      bufBE(tx.gasLimit),
      tx.to ? hexToBytes(tx.to) : new Uint8Array([]),
      bufBE(tx.value ?? 0n),
      tx.data ?? new Uint8Array([]),
      tx.accessList ?? [],
    ];
    const encodedPayload = rlp.encode(payload);
    console.log("签名payload(RLP):", "0x" + Buffer.from(encodedPayload).toString("hex"));

    // Typed Transaction v2（EIP-2718 封装）：msg = 0x02 || RLP(payload)
    // digest = keccak256(msg)
    const msg = new Uint8Array(1 + encodedPayload.length);
    msg[0] = 0x02; // typed tx prefix
    msg.set(encodedPayload, 1);
    const digest = keccak256(msg);
    console.log("长度检查:", {
      encodedPayload: encodedPayload.length,
      msg: msg.length,
      digest: digest.length,
    });

    // noble v3 的签名格式：
    // - compact：64 字节（前 32 为 r，后 32 为 s）
    // - recovered：65 字节（前 64 为 r||s，第 65 字节为恢复位 rId，等价于 yParity）
    // 以太坊 EIP-1559 需要的是 yParity（0/1）+ r + s，因此：
    // - 用 compact 提取 r/s
    // - 用 recovered 的第 65 字节提取 yParity
    let sigCompact: Uint8Array;
    let sigRec: Uint8Array;
    try {
      // 重要：prehash=false，因为我们已经传入了 keccak256(digest)
      sigCompact = secp.sign(digest, this.priv, { prehash: false });
      sigRec = secp.sign(digest, this.priv, { prehash: false, format: "recovered" });
    } catch (e: any) {
      const m = e && e.message ? e.message : String(e);
      throw new Error(`secp.sign失败: ${m}`);
    }

    // 快速自检：验证 noble 的签名/验签在 prehash=false 与 true 下的差异
    // - prehash=false：直接校验原始消息（本例符合，我们传入的 digest 是 keccak(msg)）
    // - prehash=true：内部会再做 sha256(msg)，因此对 keccak(digest) 不匹配，预期为 false
    const sanityMsg = new Uint8Array(32); sanityMsg.fill(1);
    const sanitySig = secp.sign(sanityMsg, this.priv, { prehash: false });
    const sanityPub = secp.getPublicKey(this.priv, false);
    const sanityOk = secp.verify(sanitySig, sanityMsg, sanityPub, { prehash: false });
    console.log("签名库自检(sanity verify=false):", sanityOk);
    const sanityOkSha = secp.verify(sanitySig, sanityMsg, sanityPub, { prehash: true });
    console.log("签名库自检(sanity verify=true):", sanityOkSha);

    // 再次本地验证本次交易签名（说明：prehash=false 为 true，prehash=true 为 false 是预期）
    const pub = secp.getPublicKey(this.priv, false);
    console.log("长度检查2:", { sigCompact: sigCompact.length, sigRec: sigRec.length, pub: pub.length });
    const okFalse = secp.verify(sigCompact, digest, pub, { prehash: false });
    const okTrue = secp.verify(sigCompact, digest, pub, { prehash: true });
    console.log("本地校验签名(prehash=false):", okFalse, "(prehash=true):", okTrue);

    // 从 recovered 的第 65 字节提取恢复位（yParity = rId & 1）
    // 说明：在 noble v3 中，recoverPublicKey 需要 65 字节签名；本例无需恢复公钥，仅取 yParity 即可
    const yParity = sigRec[64] & 1;

    // 从 compact 拆 r/s（各 32 字节，可能有前导零）
    const r = sigCompact.slice(0, 32);
    const s = sigCompact.slice(32, 64);
    console.log("签名digest:", bytesToHex(digest));
    console.log("签名r:", bytesToHex(r), "s:", bytesToHex(s), "yParity:", yParity);

    // RLP 要求整数使用最短编码：去掉 r 的前导零
    const rCanon = trimLeadingZeros(r);

    // 显式执行低-S 规范化：若 s > n/2，则替换为 n - s
    // 解释：避免签名可塑性；多数 ETH 节点要求低 S 签名
    let sBI = BigInt("0x" + Buffer.from(s).toString("hex"));
    if (sBI > CURVE_HALF_N) sBI = CURVE_N - sBI;
    const sCanon = bufBE(sBI);

    // 组装最终 RLP： [...payload, yParity, r, s]
    // 以太坊 RLP 中整数 0 表示为空字符串（即 []），整数 1 表示为单字节 [0x01]
    const raw = rlp.encode([...payload, yParity === 0 ? new Uint8Array([]) : new Uint8Array([1]), rCanon, sCanon]);

    // EIP-2718 类型前缀：最终原始交易为 0x02 || RLP([...])
    const out = new Uint8Array(1 + raw.length);
    out[0] = 0x02;
    out.set(raw, 1);
    return ("0x" + Buffer.from(out).toString("hex")) as Hex;
  }

  async sendEip1559(to: Address, valueWei: bigint, data?: Uint8Array): Promise<string> {
    // 1) 读取链 ID，nonce，费用建议
    const chain = await this.provider.chainId();
    if (chain !== 11155111) console.warn(`警告：当前非Sepolia（chainId=${chain})`);
    const nonce = await this.provider.nonce(this.address);
    const base = await this.provider.baseFeePerGas();
    const tip = await this.provider.maxPriorityFeePerGas();
    const maxFee = base * 2n + tip; // 简单策略：maxFee = 2*base + tip

    // 2) 估算 gasLimit（RPC 的 eth_estimateGas 要求 Hex 类型）
    const call: { from?: string; to?: string; value?: RpcHex; data?: RpcHex } = {
      from: this.address,
      to,
      value: bigintToHex(valueWei) as RpcHex,
      data: data ? (("0x" + Buffer.from(data).toString("hex")) as RpcHex) : undefined,
    };
    const gas = await this.provider.estimateGas(call);

    // 3) 构造 EIP-1559 交易并签名、发送
    const tx: Eip1559Tx = {
      chainId: chain,
      nonce,
      maxPriorityFeePerGas: tip,
      maxFeePerGas: maxFee,
      gasLimit: gas,
      to,
      value: valueWei,
      data: data ?? new Uint8Array([]),
      accessList: [],
    };
    const raw = await this.signEip1559(tx);
    return await this.provider.sendRawTransaction(raw);
  }
}

function trimLeadingZeros(b: Uint8Array): Uint8Array {
  // 去掉前导零，保证 RLP 整数的最短表示（避免无意义的 0x00 前缀）
  let i = 0;
  while (i < b.length && b[i] === 0) i++;
  return b.slice(i);
}